package cn.ryh.gat1400.config;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.ryh.gat1400.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.ZoneOffset;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Raoyh
 * @Description
 * @create 2022-09-02 9:31
 */
@Component
@Slf4j
public class AuthorizationInterceptor extends HandlerInterceptorAdapter {
    @Value("${ga1400.key}")
    private String key;
    @Value("${ga1400.userName}")
    private String userName;
    private final static String REALM_NAME = "wtone-GAT1400";
    protected static final Map<String, Long> CHANNEL_MAP = new ConcurrentHashMap<>(16);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //从header中获取auth
        String auth = request.getHeader("Authorization");
        String ip = ServletUtil.getClientIP(request);
        long time = LocalDateTimeUtil.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
        if (CHANNEL_MAP.get(ip) != null) {
            Long aLong = CHANNEL_MAP.get(ip);
            long l = 300000L;
            if (time - aLong > l) {
                return extracted(response);
            } else {
                CHANNEL_MAP.put(ip, time);
            }
            return true;
        } else {
            if (StringUtils.isNotBlank(auth) && auth.contains("Digest")) {
                String digest = auth.replace("Digest", "{");
                digest += "}";
                log.info(digest);
                String str = digest.replace(" ", "");
                str = str.substring(1, str.length() - 1).replaceAll("\"", "");
                Map map = new HashMap(16);
                String[] arr = str.split(",");
                for (String value : arr) {
                    String key = value.substring(0, value.indexOf("=")).replace(" ", "");
                    if (key.contains("{")) {
                        key = key.replace("{", "");
                    }
                    String val = value.substring(value.indexOf("=") + 1);
                    if (val.contains("\\")) {
                        val = val.replace("\\", "").replace("\\", "");
                    }
                    map.put(key, val);
                }
                if (validateAndDecode(map)) {
                    CHANNEL_MAP.put(ip, time);
                    return true;
                } else {
                    return false;
                }
            } else {
                return extracted(response);
            }
        }
    }

    private boolean extracted(HttpServletResponse response) throws IOException {
        long expiryTime = LocalDateTimeUtil.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() + 300000L;
        String signatureValue = DigestUtil.md5Hex(expiryTime + ":" + this.key);
        String nonceValue = expiryTime + ":" + signatureValue;
        String nonceValueBase64 = new String(Base64.getEncoder().encode(nonceValue.getBytes()));
        String authenticateHeader = "Digest realm=\"" + REALM_NAME + "\",qop=\"auth\",nonce=\"" + nonceValueBase64 + "\"";

        response.addHeader("WWW-Authenticate", authenticateHeader);
        response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
        return false;
    }

    boolean validateAndDecode(Map map) throws IOException {
        String username = String.valueOf(map.get("username"));
        if (!username.equals(this.userName)) {
            throw new RuntimeException("Response username " + username + " does not match system userName of " + this.userName);
        }
        String realm = String.valueOf(map.get("realm"));
        if (!realm.equals(REALM_NAME)) {
            throw new RuntimeException("Response realm name " + realm + " does not match system realm name of " + REALM_NAME);
        }
        String nonce = String.valueOf(map.get("nonce"));
        Long nonceExpiryTime;
        try {
            String nonceAsPlainText = new String(Base64.getDecoder().decode(nonce));
            String[] nonceTokens = org.springframework.util.StringUtils.delimitedListToStringArray(nonceAsPlainText, ":");

            if (nonceTokens.length != 2) {
                throw new RuntimeException("Nonce should have yielded two tokens but was " + nonceAsPlainText);
            } else {
                try {
                    nonceExpiryTime = new Long(nonceTokens[0]);
                } catch (NumberFormatException var6) {
                    throw new RuntimeException("Nonce token should have yielded a numeric first token, but was " + nonceAsPlainText);
                }
                String expectedNonceSignature = DigestUtil.md5Hex(nonceExpiryTime + ":" + this.key);
                if (!expectedNonceSignature.equals(nonceTokens[1])) {
                    throw new RuntimeException("Nonce token compromised" + nonceAsPlainText);
                }
            }
        } catch (IllegalArgumentException var7) {
            throw new RuntimeException("Nonce is not encoded in Base64; received nonce " + nonce);
        }
        return true;

    }

}
